import sys
import os
import re
import ConfigParser
import wx
import wx.lib.flatnotebook as NC
from threading import Thread
from xml.sax import parseString, handler, saxutils, SAXParseException
from wx.lib.mixins.listctrl import ColumnSorterMixin, ListCtrlAutoWidthMixin
from BitTornado.zurllib import urlopen
from LMG.GUI.Base.menu import Menu
from LMG.GUI.Base.ArtManager import ArtManager
from LMG.Utility.helpers import existsAndIsReadable

regex = re.compile("(?P<name>.*?)\[(?P<seeds>[0-9]+)/(?P<peers>[0-9]+)\]")
        
class SearchPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1, style = wx.STATIC_BORDER)

        self.searchHandler = SearchHandler()
        
        sizer = wx.BoxSizer(wx.HORIZONTAL)

        # Splitter
        self.splitter = wx.SplitterWindow(self, -1, style = wx.SP_LIVE_UPDATE)
        self.splitter.SetMinimumPaneSize(25)
        self.splitter.SetSashGravity(0.0)
        self.enginesPanel = SearchEnginesPanel(self)
        self.resultPanel = SearchListPanel(self)
        self.splitter.SplitVertically(self.enginesPanel, self.resultPanel, 150)
        
        sizer.Add(self.splitter, 1, wx.EXPAND|wx.TOP, 5)
        self.SetAutoLayout(True)
        self.SetSizerAndFit(sizer)
        
        
class SearchResultsList(wx.ListCtrl, ColumnSorterMixin, ListCtrlAutoWidthMixin):
    def __init__(self, parent):
        wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT | wx.LC_VRULES)
        ListCtrlAutoWidthMixin.__init__(self)
        ColumnSorterMixin.__init__(self, 6)

        self.parent = parent
        
        self.itemDataMap = {}
        self._col = self.parent.def_col
        
        self.InsertColumn(0, _("Name"))
        self.InsertColumn(1, _("File Size"))
        self.InsertColumn(2, _("Seeds"))
        self.InsertColumn(3, _("Peers"))
        self.InsertColumn(4, _("Category"))
        self.InsertColumn(5, _("Engine"))
        
        self.setResizeColumn(1)

        self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated)



    def OnItemActivated(self, event):
        selected = event.m_itemIndex
        data = self.itemDataMap[self.GetItemData(selected)]
        url = data[5]
        onBrowser = data[6]
        self.SetItemTextColour(selected, wx.BLUE)
        if onBrowser:
            wx.LaunchDefaultBrowser(url)
        else:
            utility.queue.addtorrents.ScanUrl(url, single = True, allowBrowser = True)
        
    def clearList(self):
        self.DeleteAllItems()
        self.itemDataMap = {}
        
    def feedList(self, data, engine):
        for item in data:

            # data
            link = item.get("Link", "---")
            
            name = item.get("Name")
            if not name:
                if '/' in link:
                    name = link.split('/')[-1]
                else:
                    name = link

            category = item.get("Category", "---")
                    
            seeders = item.get("Seeders", "---")
            leechers = item.get("Leechers", "---")

            size = item.get("Size")
            if size is not None:
                try:
                    if "G" in size:
                        size = float(size.split("G")[0].strip()) * 1024**3
                    elif "M" in size:
                        size = float(size.split("M")[0].strip()) * 1024**2
                    elif "K" in size:
                        size = float(size.split("K")[0].strip()) * 1024
                    elif "B" in size and size.find(" ") > 0:
                        size = float(size.split(" ")[0])
                    else:
                        size = float(size)
                except:
                    size = -1
                    
            if size is not None and size > -1:
                strsize = utility.size_format(size)
            else:
                strsize =  "---"

            # insert item            
            listIndex = self.InsertStringItem(sys.maxint, name)
            self.SetStringItem(listIndex, 1, strsize)    
            self.SetStringItem(listIndex, 2, seeders)    
            self.SetStringItem(listIndex, 3, leechers)    
            self.SetStringItem(listIndex, 4, category)
            self.SetStringItem(listIndex, 5, engine.viewName)
            
            self.SetItemData(listIndex, listIndex)

            # column sorter
            try:
                seeders = int(seeders)
            except:
                seeders = -1
            try:
                leechers = int(leechers)
            except:
                leechers = -1

            if type(seeders) == type(leechers) == int:
                if seeders == 0:
                    item = self.GetItem(listIndex)
                    item.SetTextColour(wx.RED)
                    self.SetItem(item)
                elif seeders > 5 and (leechers == 0 or seeders/float(leechers) > 0.5):
                    item = self.GetItem(listIndex)
                    item.SetTextColour(wx.Colour(0, 150, 0))
                    self.SetItem(item)

            self.itemDataMap[listIndex] = (name, size, seeders, leechers, category, link, engine.onbrowser)
        self.SortItems(self.GetColumnSorter())
        
    def SortItems(self, *args, **kwargs):
        super(SearchResultsList, self).SortItems(*args, **kwargs)
        ArtManager.Get().MakeAlternateList(self)
        
    def GetListCtrl(self):
        return self

    def OnSortOrderChanged(self):
        self.parent.def_col = self._col
    
class SearchListPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent.splitter, -1)
        self.parent = parent
        
        self.searches = {}
        self.recent = []
        
        self.def_col = -1
        
        sizer = wx.BoxSizer(wx.VERTICAL)

        self.searchTerm = wx.SearchCtrl(self, size=(-1,-1), style=wx.TE_PROCESS_ENTER)
        self.searchTerm.ShowCancelButton(True)
        self.searchTerm.SetDescriptiveText(_("Enter Search Term..."))
        sizer.Add(self.searchTerm, 0, wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, 0)

        nbstyle = NC.FNB_X_ON_TAB | NC.FNB_FF2 | NC.FNB_DROPDOWN_TABS_LIST | NC.FNB_NO_NAV_BUTTONS
        self.notebook = NC.FlatNotebook(self, wx.ID_ANY, style=nbstyle)
        sizer.Add(self.notebook, 1, wx.EXPAND|wx.ALL, 0)

        self.notebook.Bind(NC.EVT_FLATNOTEBOOK_PAGE_CLOSING, self.OnPageClosed)
        self.notebook._pages.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick)
        self.Bind(wx.EVT_SEARCHCTRL_CANCEL_BTN, self.OnClean, self.searchTerm)
        self.Bind(wx.EVT_TEXT_ENTER, self.onSearch, self.searchTerm)
        self.Bind(wx.EVT_MENU, self.onRecentSearch)
        
        self.SetSizerAndFit(sizer)

    def onRecentSearch(self, event):
        try:
            text = self.recent[event.GetId()-888]
            self.searchTerm.SetValue(text)
            self.searchTerm.SendTextUpdatedEvent()
        except:
            del self.recent[:]
            wx.CallAfter(self.searchTerm.SetMenu, None)

    def onSearch(self, event = None):
        term = self.searchTerm.GetValue()
        if not term:
            return

        if term in self.recent:
            self.recent.remove(term) 
        self.recent.append(term)
        if len(self.recent) > 5:
            del self.recent[0]
        self.MakeMenu()
        
        resultList = self.newSearch(term)
        resultList.clearList()
        #self.searchTerm.Clear()
        self.parent.searchHandler.search(term, self.feedList)
                
    def MakeMenu(self):
        menu = Menu()
        item = menu.Append(-1, _("Recent Searches"))
        item.Enable(False)
        menu.AppendSeparator()
        for idx, txt in enumerate(self.recent):
            menu.Append(888+idx, txt)
        menu.AppendSeparator()
        menu.Append(888+len(self.recent), _("Clean"))
        self.searchTerm.SetMenu(menu)
        
    def OnClean(self, event):
        keyword = self.searchTerm.GetValue()
        if self.searches.has_key(keyword):
            del self.searches[keyword]
        self.searchTerm.SetValue("")
        self.searchTerm.SendTextUpdatedEvent()
        
    def OnLeftDClick(self, event):
        where, tabIdx = self.notebook._pages.HitTest(event.GetPosition())
        if where == NC.FNB_TAB and tabIdx >= 0:
            self.searchTerm.SetValue(self.notebook.GetPageText(tabIdx))
            self.searchTerm.SendTextUpdatedEvent()
        event.Skip()
        
    def newSearch(self, keyword):
        if keyword in self.searches:
            page = self.searches[keyword]
            self.notebook.SetSelection(self.notebook.GetPageIndex(page))
        else:        
            page = SearchResultsList(self)
            self.notebook.AddPage(page, keyword)
            self.searches[keyword] = page
        return page

    def OnPageClosed(self, event):
        keyword = self.notebook.GetPageText(event.GetSelection())
        if keyword in self.searches:
            del self.searches[keyword]
        event.Skip()
                
    def feedList(self, data, keyword, engine):
        keyword = keyword.replace("%20", " ")
        if keyword in self.searches and data:
            wx.CallAfter(self.searches[keyword].feedList, data, engine)
    
class SearchEnginesPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent.splitter, -1)
        self.parent = parent

        sizer = wx.BoxSizer(wx.VERTICAL)
        
        # Make and layout the controls
        t = wx.StaticText(self, -1, _("Search Engines"))
        t.SetFont(wx.Font(self.GetFont().GetPointSize(), wx.SWISS, wx.NORMAL, wx.BOLD))
        sizer.Add(t, 0, wx.CENTER|wx.ALL, 5)
        
        
        self.enginesList = wx.CheckListBox(self, -1)
        sizer.Add(self.enginesList, 1, wx.EXPAND|wx.ALL, 0)

        btnsizer = wx.BoxSizer(wx.HORIZONTAL)

        AddButton = utility.makeBitmapButton(self, wx.ArtProvider.GetBitmap(wx.ART_ADD_BOOKMARK, wx.ART_TOOLBAR), _('Add'), self.onAdd)	
        btnsizer.Add(AddButton, 1, wx.EXPAND|wx.ALL, 0)
        
        RemButton = utility.makeBitmapButton(self, wx.ArtProvider.GetBitmap(wx.ART_DEL_BOOKMARK, wx.ART_TOOLBAR), _('Remove'), self.onRemove)
        btnsizer.Add(RemButton, 1, wx.EXPAND|wx.ALL, 0)

        EditButton = utility.makeBitmapButton(self, wx.ArtProvider.GetBitmap(wx.ART_HELP_SETTINGS, wx.ART_TOOLBAR), _('Edit'), self.onEdit)
        btnsizer.Add(EditButton, 1, wx.EXPAND|wx.ALL, 0)
        
        sizer.Add(btnsizer, 0, wx.EXPAND|wx.ALL, 0)

        self.SetAutoLayout(True)
        self.SetSizerAndFit(sizer)

        self.Bind(wx.EVT_LISTBOX_DCLICK, self.onEdit, self.enginesList)
        self.Bind(wx.EVT_CHECKLISTBOX, self.OnUpdateCheck, self.enginesList)
        self.load()

    def load(self):
        engines = self.parent.searchHandler.engines
        engines.sort(key = lambda x: x.viewName.lower())
        self.enginesList.Set([engine.viewName for engine in engines])
        for index in range(len(engines)):
            self.enginesList.Check(index, engines[index].active)
                
    def OnUpdateCheck(self, event):
        index = event.GetSelection()
        engines = self.parent.searchHandler.engines
        if 0 <= index < len(engines):
            engines[index].active = self.enginesList.IsChecked(index)
            self.parent.searchHandler.save()

        event.Skip()
        
    def onAdd(self, event):
        dlg = AddEngineDialog(self, _("Add Engine"))
        result = dlg.ShowModal()
        dlg.Destroy()
        if result == wx.ID_OK:
            self.parent.searchHandler.addEngine(dlg.engine)
            self.parent.searchHandler.save()
            self.load()
    
    def onRemove(self, event):
        index = self.enginesList.GetSelection()
        if index < 0:
            return
        del self.parent.searchHandler.engines[index]
        self.parent.searchHandler.save()
        self.load()
        
    def onEdit(self, event):
        index = self.enginesList.GetSelection()
        if index < 0:
            return
        engine = self.parent.searchHandler.engines[index]
        dlg = AddEngineDialog(self, _("Edit Engine"), engine.url, engine.viewName, engine.onbrowser, *engine.headers)
        result = dlg.ShowModal()
        dlg.Destroy()
        if result == wx.ID_OK:
            dlg.engine.active = engine.active
            self.parent.searchHandler.engines[index] = dlg.engine
            self.parent.searchHandler.save()
            self.load()
                
class AddEngineDialog(wx.Dialog):
    def __init__(self, parent, title, url = "", viewName = "", onbrowser = True,
                 GroupTag = "item", linkTag = "link", nameTag = "title",
                 sizeTag = "size", seedersTag = "seeders", leechersTag = "leechers",
                 categoryTag = "category", descriptionTag = "description"):
        wx.Dialog.__init__(self, parent, -1, title)

        sizer = wx.BoxSizer(wx.VERTICAL)
        
        engineBox = wx.StaticBoxSizer(wx.StaticBox(self, -1, _('Engine')), wx.VERTICAL)
        engineSizer = wx.FlexGridSizer(0, 2, 5, 5)        
        self.viewNameCtrl = wx.TextCtrl(self, -1, viewName, size = (300, -1))
        self.urlCtrl = wx.TextCtrl(self, -1, url, size = (300, -1))
        engineSizer.Add(wx.StaticText(self, -1, _("Name:")), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 3)
        engineSizer.Add(self.viewNameCtrl, 0, wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, 0)
        engineSizer.Add(wx.StaticText(self, -1, _("URL:")), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 3)
        engineSizer.Add(self.urlCtrl, 0, wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, 0)
        engineBox.Add(engineSizer, 0, wx.EXPAND|wx.ALL, 5)
        sizer.Add(engineBox, 0, wx.EXPAND|wx.ALL, 3)
        
        xmltagsBox = wx.StaticBoxSizer(wx.StaticBox(self, -1, _('XML Tags')), wx.VERTICAL)
        xmltagsSizer = wx.FlexGridSizer(0, 2, 5, 5)
        self.GroupTagCtrl = wx.TextCtrl(self, -1, GroupTag)
        self.linkTagCtrl = wx.TextCtrl(self, -1, linkTag)
        self.nameTagCtrl = wx.TextCtrl(self, -1, nameTag)
        self.sizeTagCtrl = wx.TextCtrl(self, -1, sizeTag)
        self.seedersTagCtrl = wx.TextCtrl(self, -1, seedersTag)
        self.leechersTagCtrl = wx.TextCtrl(self, -1, leechersTag)
        self.categoryTagCtrl = wx.TextCtrl(self, -1, categoryTag)
        self.descriptionTagCtrl = wx.TextCtrl(self, -1, descriptionTag)
        xmltagsSizer.Add(wx.StaticText(self, -1, _("New Group:")), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 3)
        xmltagsSizer.Add(self.GroupTagCtrl, 0, wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, 0)
        xmltagsSizer.Add(wx.StaticText(self, -1, _("Link:")), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 3)
        xmltagsSizer.Add(self.linkTagCtrl, 0, wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, 0)
        xmltagsSizer.Add(wx.StaticText(self, -1, _("Name:")), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 3)
        xmltagsSizer.Add(self.nameTagCtrl, 0, wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, 0)
        xmltagsSizer.Add(wx.StaticText(self, -1, _("Size:")), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 3)
        xmltagsSizer.Add(self.sizeTagCtrl, 0, wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, 0)
        xmltagsSizer.Add(wx.StaticText(self, -1, _("Seeders:")), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 3)
        xmltagsSizer.Add(self.seedersTagCtrl, 0, wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, 0)
        xmltagsSizer.Add(wx.StaticText(self, -1, _("Leechers:")), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 3)
        xmltagsSizer.Add(self.leechersTagCtrl, 0, wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, 0)
        xmltagsSizer.Add(wx.StaticText(self, -1, _("Category:")), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 3)
        xmltagsSizer.Add(self.categoryTagCtrl, 0, wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, 0)
        xmltagsSizer.Add(wx.StaticText(self, -1, _("*Extended*:")), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 3)
        xmltagsSizer.Add(self.descriptionTagCtrl, 0, wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, 0)
        xmltagsBox.Add(xmltagsSizer, 0, wx.EXPAND|wx.ALL, 5)
        sizer.Add(xmltagsBox, 0, wx.EXPAND|wx.ALL, 3)
        
        optionsBox = wx.StaticBoxSizer(wx.StaticBox(self, -1, _('Options')), wx.VERTICAL)
        self.onbrowser = wx.CheckBox(self, -1, _("Open results in default browser"))
        self.onbrowser.SetValue(onbrowser)
        optionsBox.Add(self.onbrowser, 0, wx.EXPAND|wx.ALL, 5)
        sizer.Add(optionsBox, 0, wx.EXPAND|wx.ALL, 3)

        sizer.Add(wx.StaticLine(self), 0, wx.GROW, 0)

        buttonSizer = wx.BoxSizer(wx.HORIZONTAL)
        okButton = wx.Button(self, wx.ID_OK, _("OK"))
        cancelButton = wx.Button(self, wx.ID_CANCEL, _("Cancel"))
        buttonSizer.Add(okButton, 0, wx.EXPAND|wx.ALL, 5)
        buttonSizer.Add(cancelButton, 0, wx.EXPAND|wx.ALL, 5)
        sizer.Add(buttonSizer, 0, wx.CENTER, 0)
        
        self.SetSizerAndFit(sizer)

        self.Bind(wx.EVT_BUTTON, self.onOK, id = wx.ID_OK)
        self.Bind(wx.EVT_BUTTON, self.onCancel, id = wx.ID_CANCEL)

    def onOK(self, event):
        if not self.urlCtrl.GetValue():
            dlg = wx.MessageDialog(self, _("You must enter a value for the URL field"), _("Input Error"), wx.OK)
            dlg.ShowModal()
            dlg.Destroy()
            self.urlCtrl.SetFocus()
            return
        headers = (self.GroupTagCtrl.GetValue(), self.linkTagCtrl.GetValue(), self.nameTagCtrl.GetValue(),
                   self.sizeTagCtrl.GetValue(), self.seedersTagCtrl.GetValue(), self.leechersTagCtrl.GetValue(),
                   self.categoryTagCtrl.GetValue(), self.descriptionTagCtrl.GetValue())
        self.engine = SearchEngine(self.urlCtrl.GetValue(), self.viewNameCtrl.GetValue(),
                                   headers, onbrowser = self.onbrowser.GetValue())
        self.EndModal(wx.ID_OK)
        
    def onCancel(self, event):
        self.EndModal(wx.ID_CANCEL)
        
class SearchEngine(object):
    def __init__(self, url = "", viewName = None,
                 headers = ("item", "link", "title", "size", "seeders", "leechers", "category", "description"),
                 active = True, onbrowser = True):
        self.url = url
        self.viewName = viewName or url
        self.headers = headers
        self.active = active
        self.onbrowser = onbrowser

    def toFile(self):
        return [self.url, self.viewName, self.headers, self.active, self.onbrowser]        
    
    def fromFile(self, data):
        self.url = data[0]
        self.viewName = data[1]
        self.headers = data[2]
        self.active = data[3]
        self.onbrowser = data[4]
        return self
        
        
class SearchHandler(object):
    def __init__(self):
        
        # Setup config file
        self.config_file = os.path.join(utility.getConfigPath(), "search.conf")

        # Load Engines
        self.engines = []
        self.load()

        self.current = {}
        
    def addEngine(self, engine):
        self.engines.append(engine)

    def search(self, keyword, callback):
        keyword = keyword.replace(" ", "%20")
        engines = [engine for engine in self.engines if engine.active]
        for engine in engines:
            if "%@" in engine.url:
                url = engine.url.replace("%@", keyword)
            else:
                url = engine.url + keyword
            searchThread = Thread(target=self.__perform_search, args = [url, engine, callback, keyword])
            searchThread.setDaemon(True)
            searchThread.start()

    def __perform_search(self, url, engine, callback, keyword):
        try:
            answer = urlopen(url, encoding = None)
            if answer:
                xmlHandler = XMLHandler(engine)
                parseString(answer.read(), xmlHandler)
                callback(xmlHandler.data, keyword, engine)
        except (IOError, ValueError, SAXParseException), e:
            print url, e
            wx.LogDebug(str(e))

    def save(self):
        """
        Save to config file 
        """
        try:
            cp = ConfigParser.ConfigParser()
            cp.add_section('SEARCH')
            i = 0
            for item in self.engines:
                cp.set('SEARCH', str(i), item.toFile())
                i = i+1
            file = open(self.config_file, 'w')
            cp.write(file)
            file.close()
        except:
            pass
    
    def load(self):
        """
        Load from config file
        """
        if not existsAndIsReadable(self.config_file):
            return
        
        try:
            cp = ConfigParser.ConfigParser()
            file = open(self.config_file, 'r')
            cp.readfp(file)
            items = cp.items('SEARCH')
            for key, param in items:
                param = eval(param)
                self.addEngine(SearchEngine().fromFile(param))
            file.close()
        except:
            wx.LogError("couldn't read from search file")
                
class XMLHandler(handler.ContentHandler):
    TORRENT = 0
    LINK = 1
    NAME = 2
    SIZE = 3
    SEEDERS = 4
    LEECHERS = 5
    CATEGORY = 6
    DESCRIPTION = 7
    def __init__(self, engine):
        handler.ContentHandler.__init__(self)
        self.headers = ("Torrent", "Link", "Name", "Size", "Seeders", "Leechers", "Category", "description")
        self.engine = engine
        self.currentHeader = None
        self.data = []
        
    def startElement(self, name, attrs):
        if name != self.engine.headers[self.TORRENT] and not self.data:
            return
    
        if name == self.engine.headers[self.TORRENT]:
            assert self.currentHeader == None
            self.currentHeader = self.TORRENT
            self.data.append({})
            
        elif name == self.engine.headers[self.LINK]:
            self.currentHeader = self.LINK
            obj = self.data[-1]
            if attrs.get("href"): 
                obj[self.headers[self.LINK]] = attrs["href"]
            elif attrs.get("url"): 
                obj[self.headers[self.LINK]] = attrs["url"]
            if attrs.get("length")  and not obj.has_key(self.headers[self.SIZE]):
                size = int(attrs["length"]) / (1024*1024)
                obj[self.headers[self.SIZE]] = str(size) + " MB"
                
        elif name == self.engine.headers[self.NAME]:
            self.currentHeader = self.NAME
            
        elif name == self.engine.headers[self.SIZE]:
            self.currentHeader = self.SIZE
            
        elif name == self.engine.headers[self.SEEDERS]:
            self.currentHeader = self.SEEDERS
            
        elif name == self.engine.headers[self.LEECHERS]:
            self.currentHeader = self.LEECHERS
            
        elif name == self.engine.headers[self.CATEGORY]:
            self.currentHeader = self.CATEGORY

        elif name == self.engine.headers[self.DESCRIPTION]:
            self.currentHeader = self.DESCRIPTION
        else:
            self.currentHeader = None
            
    def endElement(self, name):
        # Group ended
        if name == self.engine.headers[self.TORRENT]:
            self.currentHeader = None
        
    def characters(self, content):
        try:
            self._characters(content)
        except:
            pass

    def _characters(self, content):
        def isnumber(s):
            try:
                float(s)
                return True
            except:
                return False
        
        content = content.strip()

        # Bad state
        if self.currentHeader == None or not content or not self.data:
            return

        obj  = self.data[-1]
        
        # Already have this data
        if obj.has_key(self.headers[self.currentHeader]) \
           and self.currentHeader != self.DESCRIPTION:
            return

        # fix content
        content = saxutils.unescape(content)
        
        # Name
        if self.currentHeader == self.NAME:
            if not self.engine.headers[self.SEEDERS] and not self.engine.headers[self.LEECHERS]:
                match = regex.match(content)
                if match:
                    obj[self.headers[self.NAME]] = match.group(1)
                    obj[self.headers[self.SEEDERS]] = match.group(2)
                    obj[self.headers[self.LEECHERS]] = match.group(3)
                else:
                    obj[self.headers[self.NAME]] = content
            else:
                obj[self.headers[self.NAME]] = content

        # Description
        elif self.currentHeader == self.DESCRIPTION:
            sizekeys = ("size:", "filesize:", "file size:")
            seedkeys = ("seeders:", "seeds:", "seed:")
            peerkeys = ("leechers:", "peers:", "leech:", "downloaders:")
            content = content.replace(" :", ":").lower()
            content = content.split(" ")

            for index in xrange(len(content)):
                item = content[index]
                if not item.strip():
                    continue
                
                data = ""
                for key in sizekeys:
                    if item == key:
                        data = content[index+1].upper()
                        if isnumber(data):
                            data += " " + content[index+2].upper()
                    elif item.startswith(key):
                        data = item.split(key)[1].upper()
                        if isnumber(data):
                            data += " " + content[index+1].upper()
                    if data:
                        obj[self.headers[self.SIZE]] = data

                data = ""
                for key in seedkeys:
                    if item == key:
                        data = content[index+1]
                    elif item.startswith(key):
                        data = item.split(key)[1]
                    if data:
                        obj[self.headers[self.SEEDERS]] = data

                data = ""
                for key in peerkeys:
                    if item == key:
                        data = content[index+1]
                    elif item.startswith(key):
                        data = item.split(key)[1]
                    if data:
                        obj[self.headers[self.LEECHERS]] = data
        # Other
        else:
            obj[self.headers[self.currentHeader]] = content
